//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
//  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//  PARTICULAR PURPOSE.
//
//  Copyright  1998 - 2005  Microsoft Corporation.  All Rights Reserved.
//
//  FILE: xmlhandler.cxx
//
//
//  PURPOSE: Helper Class that handles all functionality related to
//  processing of input PrintTicket/PrintCapabilities XML documents.
//
//
//  Functions: Public and Private functions of class OEMPTXMLHandler.
//

#include "precomp.hxx"

#include "globals.hxx"
#include "printschema.hxx"
#include "xmlhandler.hxx"

// This indicates to Prefast that this is a usermode driver file.
__user_driver;


//Disable the warnings generated by PFEFAST for conversion between wchar_t * and BSTR
//because MSXML defines all its interfaces parameters as BSTR but officially declares itself
//to be wchar_t safe.

#pragma prefast(disable:__WARNING_WCHAR_TO_BSTR)

//
// MSXML makes the guarantee that we can call it's methods with LPTSTRs instead
// of BSTRs, so we need to do a const cast whenever we pass our strings to MSXML.
//
#define MSXMLSTR(lptstr_value) (const_cast<BSTR>(lptstr_value))

/*++
Routine Name:
    OEMPTXMLHandler
Routine Description:
    Print Ticket Handler Constructor


Arguments:
    None

Return Value:
    None

--*/

OEMPTXMLHandler::OEMPTXMLHandler() :
    m_pRootDocument(NULL),
    m_pRootElement(NULL),
    m_pNSManager(NULL),
    m_dwNextIndex(0)
{
}

/*++
Routine Name:
    ~OEMPTXMLHandler(
Routine Description:
    Print Ticket Handler Destructor

Arguments:
    None

Return Value:
    None

--*/

OEMPTXMLHandler::~OEMPTXMLHandler()
{
    if (m_pRootDocument)
    {
        m_pRootDocument->Release();
        m_pRootDocument = NULL;
    }
    if (m_pRootElement)
    {
        m_pRootElement->Release();
        m_pRootElement = NULL;
    }
    if (m_pNSManager)
    {
        m_pNSManager->Release();
        m_pNSManager = NULL;
    }
}

/*++
Routine Name:
    SetRoot

Routine Description:

    Initialization routine. Sets root of the DOM tree. Alos creates an instance of xml namespace manager
    using plug-in helper interface method.

    Note:  This routine MUST BE called before any other xml handling routines are called.
    Note also that it is not necessary to call CoInitializeEx because Unidrv has already
    made that call while creating the DOM tree with appropriate arguments passed on to it
    and when Unidrv makes a call to one of plug-in interface routines, since plug-in is
    running in the same thread, it does not need to make that call again
    In fact the plug-in SHOULD NOT make that call to CoInitializeEx , because Unidrv must have made
    that call with some particular concurrency model settings which plug-in cannot find out,
    so the plug-in might end up changing the concurrency setting made by Unidrv.

Arguments:
    pRoot - Root of the input DOM tree.


Return Value:
    HRESULT Completion status code

--*/
HRESULT
OEMPTXMLHandler::SetRoot(
    __in IXMLDOMDocument2 *pRoot,
    __in IPrintCoreHelper *pHelper
    )
{
    HRESULT hr = S_OK;

    //
    // check if input paramters are not NULL
    //
    if (!pRoot || !pHelper)
    {
        return E_INVALIDARG;
    }

    m_pRootDocument = pRoot;

    m_pRootDocument->AddRef();


    //
    // Make use of Plug-in Helper Interface method provided by Unidrv to create an Instance of MSXML Namespace Manager.
    // The plug-in must make use of Helper Interface provided by Unidrv while creating instance of namespacemanager
    // of MSXML DOM document, the plug-in should not load MSXML6.DLL by itself but should rely on Unidrv for
    // doing that
    //
    if (SUCCEEDED(hr))
    {
        //
        // Note: Current versions of Unidrv support MSXML6 hence plug-in should also support that and write its
        // code in compliance with MSXML6
        //
        hr = pHelper->CreateInstanceOfMSXMLObject(CLSID_MXNamespaceManager60, NULL, NULL, IID_IMXNamespaceManager, (void**)&m_pNSManager);
    }

    //
    // Get the Root Element of the document and store it in pRootElement.
    //
    if (SUCCEEDED(hr))
    {
        hr = m_pRootDocument->get_documentElement(&m_pRootElement);
    }

    if (FAILED(hr))
    {
        if (m_pRootDocument)
        {
            m_pRootDocument->Release();
            m_pRootDocument = NULL;
        }

        if (m_pRootElement)
        {
            m_pRootElement->Release();
            m_pRootElement = NULL;
        }

        if (m_pNSManager)
        {
            m_pNSManager->Release();
            m_pNSManager = NULL;
        }
    }

    return hr;
}

/*++
Routine Name:
    CreateFeatureNode

Routine Description:

    Creates a new Feature Node

Arguments:
    pParent - parent node of the new feature node, if NULL, root is considered as parent
    pszNamespaceURI - namesapce URI to which new node belongs
    pszFeatureName - local name of the new feature node
    ppFeatureNode - out pointer to newly created node

Return Value:
    HRESULT Completion status code
    S_OK on success
    E_* on failure

--*/

HRESULT OEMPTXMLHandler::CreateFeatureNode(
    __in_opt IXMLDOMElement *pParent,
    __in PCWSTR pszNamespaceURI,
    __in PCWSTR pszFeatureName,
    __out_opt IXMLDOMElement **ppFeatureNode
    )
{
    HRESULT hr = S_OK;
    IXMLDOMElement *pNewElement = NULL;
    BSTR qName = NULL;

    if (!pszFeatureName)
    {
        return E_INVALIDARG;
    }

    if (!pParent)
    {
        pParent = m_pRootElement;
    }


    hr = CreateXMLElement(pParent, printschema::FEATURE_ELEMENT_NAME, printschema::FRAMEWORK_URI, &pNewElement);

    if (SUCCEEDED(hr))
    {
        hr = CreateQName(pNewElement, pszNamespaceURI, pszFeatureName, &qName);
    }

    if (SUCCEEDED(hr))
    {

        // The 'name' attibute node is not namespace qualified; it
        // belongs to the namespace of its parent (feature/option)
        // node.

        hr = CreateXMLAttribute(pNewElement, printschema::NAME_ATTRIBUTE_NAME, L"", qName);
    }

    if (qName)
    {
        SysFreeString(qName);
        qName = NULL;
    }

    if (SUCCEEDED(hr) && ppFeatureNode)
    {
        *ppFeatureNode = pNewElement;
    }
    else if (pNewElement)
    {
        pNewElement->Release();
    }

    return hr;

}

/*++
Routine Name:
    CreateOptionNode

Routine Description:

    Creates a new Option Node

Arguments:
    pParent - parent node of the new Option node
    pszNamespaceURI - namesapce URI to which new node belongs
    pszFeatureName - local name of the new Option node
    ppOptionElement - out pointer to newly created node

Return Value:
    HRESULT Completion status code
    S_OK on success
    E_* on failure

--*/

HRESULT OEMPTXMLHandler::CreateOptionNode(
    __in IXMLDOMElement *pParent,
    __in PCWSTR pszNamespaceURI, //print schema URI that corresponds to prefix "prn:"
    __in PCWSTR pszOptionName, //"TopLeft" etc
    __out_opt IXMLDOMElement **ppOptionElement
    )
{
    HRESULT hr = S_OK;
    IXMLDOMElement *pNewElement = NULL;
    BSTR qName = NULL;

    if (!pParent)
    {
        return E_INVALIDARG;
    }

    hr = CreateXMLElement(pParent, printschema::OPTION_ELEMENT_NAME, printschema::FRAMEWORK_URI, &pNewElement);

    if (SUCCEEDED(hr))
    {
        hr = CreateQName(pNewElement, pszNamespaceURI, pszOptionName, &qName);
    }

    if (SUCCEEDED(hr))
    {
        // The 'name' attibute node is not namespace qualified; it
        // belongs to the namespace of its parent (feature/option)
        // node.

        hr = CreateXMLAttribute(pNewElement, printschema::NAME_ATTRIBUTE_NAME, L"", qName);
    }

    if (qName)
    {
        SysFreeString(qName);
        qName = NULL;
    }

    if (SUCCEEDED(hr) && ppOptionElement)
    {
        *ppOptionElement = pNewElement;
    }
    else if (pNewElement)
    {
        pNewElement->Release();
    }

    return hr;
}

/*++
Routine Name:
    CreateXMLElement

Routine Description:
    Generic method to create new XML element node

Arguments:

    pParent - pointer to the parent node of the new nod eto be created, if NULL root of DOM tree is considered as parent
    pszElementName - local name of the newly created element
    pszNamespaceURI - namespace URI that the new element belongs to, the caller can only specify the namespace URI
                      and not the prefix
    ppCreatedElement - double pointer to the newly created element returned by the procedure

Return Value:
    HRESULT Completion status code
    S_OK - If the new element node is created successfully
    E_* -  on failure

--*/

HRESULT OEMPTXMLHandler::CreateXMLElement(
    __in IXMLDOMElement *pParent,
    __in PCWSTR pszElementName,
    __in PCWSTR pszNamespaceURI,
    __out IXMLDOMElement **ppCreatedElement
    )
{
    HRESULT hr = S_OK;
    IXMLDOMNode *pNewNode = NULL;
    BSTR bstrQName = NULL;

    if (!pParent || !pszElementName || !pszNamespaceURI || !ppCreatedElement)
    {
        return E_INVALIDARG;
    }

    //
    // create XML Qname
    //
    hr = CreateQName(pParent, pszNamespaceURI, pszElementName, &bstrQName);

    if (SUCCEEDED(hr))
    {
        VARIANT nodeType;
        VariantInit(&nodeType);
        V_VT(&nodeType) = VT_I4;
        V_I4(&nodeType) = NODE_ELEMENT;
        hr = m_pRootDocument->createNode(nodeType, bstrQName, MSXMLSTR(pszNamespaceURI), &pNewNode);
        if (SUCCEEDED(hr))
            VariantClear(&nodeType);
    }

    if (SUCCEEDED(hr))
        hr = pParent->appendChild(pNewNode, NULL);


    if (SUCCEEDED(hr))
    {
        //
        // we need to return Interface ptr to IXMLDOMElement.
        //
        hr = pNewNode->QueryInterface(IID_IXMLDOMElement, (void **)ppCreatedElement);

        //
        // if interface is not supported, E_* will be returned in hr,
        // we simply return that to the caller.
        //
    }

    if (pNewNode)
    {
        //
        // release reference to local node Created since it is no longer being used
        //
        pNewNode->Release();
    }

    if (bstrQName)
    {
        SysFreeString(bstrQName);
        bstrQName = NULL;
    }

    return hr;

}

/*++
Routine Name:
    CreateQName

Routine Description:

    Creates XML qname for given local name and namespace URI
    Note: It is assumed that a prefix for given URI is already defined in root of DOM document
    either by Unidrv or by plug-in itself.
    Routine fails if it doesnot find a predefined prefix for given URI.

Arguments:
    pElement -  context node
    pszUri - Uri
    pszLocalName - local name
    pQName - a pointer to resultant qname string defined by the routine

Return Value:
    HRESULT Completion status code

    S_OK - On success
    E_FAIL - on failure to create Qname

--*/
HRESULT OEMPTXMLHandler::CreateQName(
    __in IXMLDOMElement *pElement,
    __in PCWSTR pszUri,
    __in PCWSTR pszLocalName,
    __out BSTR *pQName
    )
{
    HRESULT hr = S_OK;
    BSTR bstrPrefixString = NULL;
    size_t prefixLen = 0;
    // Is it the default namespace?  If so, don't stick a semicolon in the string
    size_t localNameLen = 0;

    if (!pszUri || !pszLocalName || !pQName)
    {
        return E_INVALIDARG;
    }

    *pQName = NULL;

    //
    // See if a prefix is already defined for the given URI
    //

    hr = getPrefix(pElement, pszUri, &bstrPrefixString);

    //
    // It is possible that during validation the prefix might not
    // already be defined if we're converting public features to GPD
    // features.  In that case, we need to add a definition for the
    // feature namespace.
    //

    if (S_FALSE == hr)
    {
        hr = declarePrefix(pElement, pszUri, NULL, &bstrPrefixString);
    }

    if (SUCCEEDED(hr))
    {
        //
        // StrSafe function StringCchLength checks if the length of string is less than
        // maximum length specified, and returns S_OK if the input string is non NULL and
        // length is less that maximum length specified.
        // It returns length of string EXCLUDING null terminator
        //
        hr = StringCchLength(pszLocalName, STR_MAX_CCH, &localNameLen);
    }

    if (SUCCEEDED(hr))
    {
        if (bstrPrefixString)
        {
            hr = StringCchLength(bstrPrefixString, STR_MAX_CCH, &prefixLen);
        }
    }

    if (SUCCEEDED(hr))
    {
        if (prefixLen > 0)
        {
            // non-default namespace

            UINT cchLength = 0;

            if (SUCCEEDED(hr = SizeTAdd(prefixLen, localNameLen, &localNameLen)) &&
                SUCCEEDED(hr = SizeTAdd(localNameLen, 2, &localNameLen)) &&
                SUCCEEDED(hr = SizeTToUInt(localNameLen, &cchLength)))
            {

                *pQName = SysAllocStringLen(NULL, cchLength);

                if (*pQName)
                {
                    // we got a terminator character as part of SysAllocString.
                    hr = StringCchPrintf(*pQName, cchLength, L"%s:%s", bstrPrefixString, pszLocalName);
                }
                else
                {
                    hr = E_OUTOFMEMORY;
                }
            }
        }
        else
        {
            // Default namespace ...

            UINT cchLength = 0;

            if (SUCCEEDED(hr = SizeTAdd(localNameLen, 1, &localNameLen)) &&
                SUCCEEDED(hr = SizeTToUInt(localNameLen, &cchLength)))
            {
                *pQName = SysAllocStringLen(NULL, cchLength);

                if (*pQName)
                {
                    // we got a terminator character as part of SysAllocString.
                    hr = StringCchPrintf(*pQName, cchLength, L"%s", pszLocalName);
                }
                else
                {
                    hr = E_OUTOFMEMORY;
                }
            }
        }
    }

    if (bstrPrefixString)
    {
        SysFreeString(bstrPrefixString);
        bstrPrefixString = NULL;
    }

    if (FAILED(hr))
    {
        if (*pQName)
        {
            SysFreeString(*pQName);
            *pQName = NULL;
        }
    }

    return hr;
}

/*++
Routine Name:
    CreateXMLAttribute

Routine Description:
    Creates an attribute for the given XML element.

Arguments:

    pElement - pointer to the element node to which an attribute is to be added
    pszAttributeName - local name of the attribute
    pszNamespaceURI - namespace URI to which the attribute belongs
    pszAttributeValue - value of the attribute

Return Value:
    HRESULT Completion status code
    S_OK - on success
    E_* - on failure

--*/

HRESULT OEMPTXMLHandler::CreateXMLAttribute(
    __in IXMLDOMElement *pElement,
    __in PCWSTR pszAttributeName, //"name="
    __in PCWSTR pszNamespaceURI,  //"pt:"
    __in PCWSTR pszAttributeValue //"prn:"
    )
{
    HRESULT hr = S_OK;
    IXMLDOMAttribute *pNewAttribute = NULL ;
    IXMLDOMNode *pNewNode = NULL ;

    if (!pElement || !pszAttributeName || ! pszNamespaceURI || !pszAttributeValue)
    {
        return E_INVALIDARG;
    }

    VARIANT nodeType;
    VariantInit(&nodeType);
    V_VT(&nodeType) = VT_I4;
    V_I4(&nodeType) = NODE_ATTRIBUTE;
    hr = m_pRootDocument->createNode(nodeType, MSXMLSTR(pszAttributeName), MSXMLSTR(pszNamespaceURI), &pNewNode);
    if (SUCCEEDED(hr))
        VariantClear(&nodeType);

    if (SUCCEEDED(hr))
    {
        hr = pNewNode->QueryInterface(IID_IXMLDOMAttribute, (void **)&pNewAttribute);
    }

    if (SUCCEEDED(hr))
    {
        VARIANT attrType;
        VariantInit(&attrType);
        V_VT(&attrType) = VT_BSTR;
        V_BSTR(&attrType) = SysAllocString(pszAttributeValue);
        hr = pNewAttribute->put_value(attrType);
        if (SUCCEEDED(hr))
            VariantClear(&attrType);
    }

    if (SUCCEEDED(hr))
    {
        hr = pElement->setAttributeNode(pNewAttribute, NULL);
    }

    if (SUCCEEDED(hr))
    {
        if (pNewAttribute)
        {
            pNewAttribute->Release();
        }

    }
    else if (pNewAttribute)
        pNewAttribute->Release();

    if (pNewNode)
        pNewNode->Release();

    return hr;
}

/*++
Routine Name:
    GetXMLElement
Routine Description:
    Returns an XML element with given namespace URI and given attribute

Arguments:

    pParent - Parent of node to be found
    pszElementNamespaceURI -  Namespace URI to which the element belongs
    pszElementName - Local Name of the element
    pszAttrNamespaceURI - Namespace URI to which the attribute of the element belongs
    pszAttrName - local name of the element attribute
    ppElement - out pointer to xml element so retrieved

Return Value:
    HRESULT Completion status code
    S_OK - On Successfully locating XML node specified
    S_FALSE - If the specified XML node is not found
    E_* - On Failure

    Note: Since the module returns S_FALSE if the specified node is not found, the caller must be
    careful to check hr status to be S_OK before proceeding, just checking for SUCCEEDED(hr) might
    give wrong results

--*/
HRESULT OEMPTXMLHandler::GetXMLElement(
    __in IXMLDOMElement *pParent,
    __in PCWSTR pszElementNamespaceURI,
    __in PCWSTR pszElementName,
    __in PCWSTR pszAttrNamespaceURI,
    __in PCWSTR pszAttrName,
    __deref_out_opt IXMLDOMElement **ppElement
    ) const
{
    HRESULT hr = S_OK;
    LONG cChildren = 0;
    IXMLDOMNodeList *pChildren = NULL;


    if (!pParent || !pszElementNamespaceURI || !pszElementName || !pszAttrNamespaceURI || !pszAttrName)
    {
        return E_INVALIDARG;
    }

    *ppElement = NULL;

    //
    // Get a list of child nodes for the current DOM node
    //
    hr = pParent->get_childNodes(&pChildren);

    //
    // Get number of child nodes
    //
    if (SUCCEEDED(hr))
    {
        hr = pChildren->get_length(&cChildren);
    }

    if (SUCCEEDED(hr))
    {
        LONG i = 0;
        BOOL bFound = FALSE;

        for(i = 0;i < cChildren && !bFound;i++)
        {
            IXMLDOMNode *pCurNode;
            IXMLDOMElement *pCurElement = NULL;

            hr = pChildren->get_item(i, &pCurNode);

            if (SUCCEEDED(hr))
            {
                hr = pCurNode->QueryInterface(IID_IXMLDOMElement, (void **)&pCurElement);
            }
            if (S_OK == hr)
            {
                BSTR bstrElementName = NULL;
                BSTR bstrElementNamespaceURI = NULL;

                //
                // retrieve element name and namespace URI
                //
                hr = pCurElement->get_baseName(&bstrElementName);

                if (SUCCEEDED(hr))
                {
                    hr = pCurElement->get_namespaceURI(&bstrElementNamespaceURI);
                }

                if (SUCCEEDED(hr))
                {

                    if (0 == wcscmp(bstrElementName, pszElementName) && 0 == wcscmp(bstrElementNamespaceURI, pszElementNamespaceURI))
                    {
                        //
                        //check if element name matches
                        //
                        BSTR bstrAttrName;
                        BSTR bstrAttrNamespaceURI;
                        hr = GetXMLAttribute(pCurElement,&bstrAttrNamespaceURI,&bstrAttrName);
                        if (SUCCEEDED(hr))
                        {
                            if (0 == wcscmp(bstrAttrName, pszAttrName) && 0 == wcscmp(pszAttrNamespaceURI, bstrAttrNamespaceURI))
                            {
                                bFound = TRUE;
                                *ppElement = pCurElement;
                                (*ppElement)->AddRef();
                            }
                            else
                            {
                                hr = S_FALSE;
                            }
                        }

                        SysFreeString(bstrAttrName);
                        bstrAttrName = NULL;

                        SysFreeString(bstrAttrNamespaceURI);
                        bstrAttrNamespaceURI = NULL;
                    }
                }

                SysFreeString(bstrElementName);
                bstrElementName = NULL;

                SysFreeString(bstrElementNamespaceURI);
                bstrElementNamespaceURI = NULL;

                if (pCurElement)
                {
                    pCurElement->Release();
                }
            }

            if (pCurNode)
            {
                pCurNode->Release();
            }
        }
    }

    if (pChildren)
        pChildren->Release();

    if (SUCCEEDED(hr) && (*ppElement))
    {
        hr = S_OK;
    }
    else
    {
        hr = S_FALSE;
    }

    return hr;

}

/*++
Routine Name:
    GetXMLElementWithoutAttribute

Routine Description:
    Returns an XML element with given namespace URI.

Arguments:

    pParent - Parent of node to be found
    pszElementNamespaceURI -  Namespace URI to which the element belongs
    pszElementName - Local Name of the element
    ppChildElement - out pointer to xml element so retrieved

Return Value:
    HRESULT Completion status code
    S_OK - On Successfully locating XML node specified
    S_FALSE - If the specified XML node is not found
    E_* - On Failure

    Note: Since the module returns S_FALSE if the specified node is not found, the caller must be
    careful to check hr status before proceeding, just checking for SUCCEEDED(hr) might
    give wrong results

--*/
HRESULT OEMPTXMLHandler::GetXMLElementWithoutAttribute(
    __in IXMLDOMElement *pContext,
    __in PCWSTR pszElementNamespace,
    __in PCWSTR pszElementName,
    __deref_out_opt IXMLDOMElement **ppChildElement
    ) const
{
    if (!pContext || !pszElementName || !pszElementNamespace || !ppChildElement)
    {
        return E_INVALIDARG;
    }

    HRESULT hr = S_OK;
    LONG cChildren = 0;
    IXMLDOMNodeList *pContextChildren;
    *ppChildElement = NULL;

    //
    // Get the list of child nodes
    //
    hr = pContext->get_childNodes(&pContextChildren);

    if (SUCCEEDED(hr))
        hr = pContextChildren->get_length(&cChildren);

    if (SUCCEEDED(hr))
    {
        for(LONG i = 0;i < cChildren && !(*ppChildElement);i++)
        {

            IXMLDOMNode *pCurrentNode;
            IXMLDOMElement *pCurrentElement;
            hr = pContextChildren->get_item(i, &pCurrentNode);

            //
            // See if its a DOM Element.  Don't set hr here because we
            // fully expect that not everything in the context is an element.
            //
            if (SUCCEEDED(hr) && S_OK == pCurrentNode->QueryInterface(IID_IXMLDOMElement, (void**)&pCurrentElement))
            {
                BSTR pbstrBaseName = NULL;
                BSTR pbstrNamespaceURI = NULL;

                hr = pCurrentElement->get_baseName(&pbstrBaseName);

                if (SUCCEEDED(hr))
                {
                    hr = pCurrentElement->get_namespaceURI(&pbstrNamespaceURI);
                }
                if (SUCCEEDED(hr) &&
                    0 == wcscmp(pbstrNamespaceURI, pszElementNamespace) &&
                    0 == wcscmp(pbstrBaseName, pszElementName))
                {
                    *ppChildElement = pCurrentElement;
                    (*ppChildElement)->AddRef();
                }

                pCurrentElement->Release();

                SysFreeString(pbstrBaseName);
                pbstrBaseName = NULL;

                SysFreeString(pbstrNamespaceURI);
                pbstrNamespaceURI = NULL;
            }

            if (pCurrentNode)
            {
                pCurrentNode->Release();
            }
        }
    }

    if (pContextChildren)
    {
        pContextChildren->Release();
    }

    if (SUCCEEDED(hr))
    {
        if (*ppChildElement)
        {
            hr = S_OK;
        }
        else
        {
            hr = S_FALSE;
        }
    }

    return hr;
}

/*++
Routine Name:
    GetXMLAttribute

Routine Description:
    Returns an XML Attribute node with given namespace URI.

Arguments:

    pParent - Parent of node to be found
    pszAttrNamespaceURI -  Namespace URI to which the attribute belongs
    pszAttrName - Local Name of the attribute
    ppChildElement - out pointer to xml node so retrieved

Return Value:
    HRESULT Completion status code
    S_OK - On Success
    E_* - On Failure

--*/
HRESULT OEMPTXMLHandler::GetXMLAttribute(
    __in IXMLDOMElement *pElement,
    __deref_out_opt BSTR *ppAttrNamespaceURI,
    __deref_out_opt BSTR *ppAttrName
    ) const
{
    HRESULT hr = S_OK;
    IXMLDOMNamedNodeMap *pAttrMap = NULL;
    IXMLDOMNode *pAttrNode = NULL;

    if (!pElement || !ppAttrNamespaceURI || !ppAttrName)
    {
        return E_INVALIDARG;
    }

    *ppAttrNamespaceURI = NULL;
    *ppAttrName = NULL;

    hr = pElement->get_attributes(&pAttrMap);

    if (SUCCEEDED(hr))
    {
        hr = pAttrMap->getNamedItem(MSXMLSTR(printschema::NAME_ATTRIBUTE_NAME),&pAttrNode);
    }

    if (S_OK == hr)
    {
        BSTR bstrAttrQName = NULL;
        IXMLDOMAttribute *pAttribute = NULL;

        hr = pAttrNode->QueryInterface(IID_IXMLDOMAttribute, (void **)&pAttribute);

        if (SUCCEEDED(hr))
            hr = pAttribute->get_text(&bstrAttrQName);

        if (SUCCEEDED(hr))
            hr = getUri(pElement, bstrAttrQName, ppAttrNamespaceURI);

        if (SUCCEEDED(hr))
            hr = getLocalName(bstrAttrQName, ppAttrName);

        if (pAttribute)
            pAttribute->Release();

        if (bstrAttrQName)
        {
            SysFreeString(bstrAttrQName);
            bstrAttrQName = NULL;
        }
    }

    if (pAttrMap)
        pAttrMap->Release();
    if (pAttrNode)
        pAttrNode->Release();

    if (FAILED(hr))
    {
        if (*ppAttrNamespaceURI)
        {
            SysFreeString(*ppAttrNamespaceURI);
            *ppAttrNamespaceURI = NULL;
        }
        if (*ppAttrName)
        {
            SysFreeString(*ppAttrName);
            *ppAttrName = NULL;
        }
    }
    return hr;
}

/*++
Routine Name:
    GetFeatureNode

Routine Description:

    Returns the feature node with specified attribute under printschema namespace

Arguments:
    pParent - parent node of the feature node to be located
    pszAttrNamespaceURI - namespace URI for the attribute node of given feature node
    pszAttrName - attribute name for given feature node
    ppElement - OUT pointer to feature node to be returned

Return Value:
    HRESULT Completion status code
    S_OK - On Successfully locating Feature node
    S_FALSE - If Feature node is not found
    E_* - On Failure


--*/
HRESULT OEMPTXMLHandler::GetFeatureNode(
    __in_opt IXMLDOMElement *pParent,
    __in PCWSTR pszAttrNamespaceURI,
    __in PCWSTR pszAttrName,
    __deref_out_opt IXMLDOMElement **ppElement
    ) const
{
    HRESULT hr = S_OK;

    //
    // If no parent is specified, set root of DOM tree as parent
    //
    if (!pParent)
    {
        pParent = m_pRootElement;
    }

    hr = GetXMLElement(pParent, printschema::FRAMEWORK_URI, printschema::FEATURE_ELEMENT_NAME, pszAttrNamespaceURI, pszAttrName, ppElement);

    return hr;
}

/*++
Routine Name:
    GetOptionNode

Routine Description:

    Returns the option node under printschema namespace

Arguments:
    pParent - parent node of the option node to be located
    ppElement - OUT pointer to option node to be returned

Return Value:
    HRESULT Completion status code
    S_OK - On Successfully locating Option node
    S_FALSE - If Option node is not found
    E_* - On Failure

--*/
HRESULT
OEMPTXMLHandler::GetOptionNode(
    __in IXMLDOMElement *pParent,
    __deref_out_opt IXMLDOMElement **ppElement
    ) const
{
    return GetXMLElementWithoutAttribute(pParent, printschema::FRAMEWORK_URI, printschema::OPTION_ELEMENT_NAME, ppElement);
}

/*++

Routine Name:
    getLocalName
Routine Description:
    Returns Local name for input XML Qname.

Arguments:
    pszQName - Qname for which local name is to be found out
    ppbstrLocalName - OUT pointer to local name

Return Value:
    HRESULT Completion status code
    S_OK - On Success
    E_* - On Failure

--*/
HRESULT
OEMPTXMLHandler::getLocalName(
    __in PCWSTR pszQName,
    __out BSTR *ppbstrLocalName
    ) const
{
    HRESULT hr = S_OK;

    if (!pszQName || !ppbstrLocalName)
    {
        return E_INVALIDARG;
    }

    *ppbstrLocalName = NULL;

    PCWSTR pszStartPos = wcschr(pszQName,L':');

    if (pszStartPos)
    {
        pszStartPos++;
    }
    else
    {
        pszStartPos = pszQName;
    }

    *ppbstrLocalName = SysAllocString(pszStartPos);

    if (!(*ppbstrLocalName))
    {
        hr = E_OUTOFMEMORY;
    }

    return hr;
}


/*++
Routine Name:
    getUri
Routine Description:
    Returns namespace URI for given input Qname and in context of given XML DOM node.
Arguments:
    pContext - Input XML DOM node for current context
    pszQName - input qname
    pbstrURI - OUT pointer to URI to be returned
Return Value:

    S_OK if found
    S_FALSE if not found
    E_OUTOFMEMORY if the string could not be allocated
    E_INVALIDARG if a non-optional param is null.

Notes:

    This routine assumes that the string has already been
    validated, which should have happened in the managed code base.
    The string should either be of the form "prefix:localname",
    or "localname"

--*/
HRESULT
OEMPTXMLHandler::getUri(
    IXMLDOMElement *pContext,
    __in PCWSTR pszQName,
    __out BSTR *pbstrURI
    ) const

{
    HRESULT hr = S_OK;
    BOOL bNodePushed = FALSE;

    if (!(pContext && pbstrURI && pszQName))
    {
        return E_INVALIDARG;
    }

    PWSTR pszSplitPos = wcschr(pszQName, L':');
    INT cchUri = 0;
    *pbstrURI = NULL;

    //
    // if splitpos is null, then there is no namespace
    // prefix, which means return the default namespace.
    //
    wchar_t wchSwapChar = L'\0';
    if (pszSplitPos)
    {
        wchSwapChar = *pszSplitPos;
        *pszSplitPos = (wchar_t)0;
    }
    else
    {
        pszQName = L"";
    }

    hr = m_pNSManager->pushNodeContext(pContext, TRUE);

    if (SUCCEEDED(hr))
    {
        bNodePushed = TRUE;
        // get the length first, then alloc the string
        hr = m_pNSManager->getURI(pszQName, NULL, NULL, &cchUri);
    }

    if (S_OK == hr)
    {
        if (SUCCEEDED(hr))
        {
            // account for null terminator
            cchUri += 1;

            *pbstrURI = SysAllocStringLen(NULL, cchUri);
            if (!(*pbstrURI))
            {
                hr = E_OUTOFMEMORY;
            }
        }

        if (SUCCEEDED(hr))
        {
            hr = m_pNSManager->getURI(pszQName, NULL, *pbstrURI, &cchUri);
        }

        if (pszSplitPos)
        {
            *pszSplitPos = wchSwapChar;
        }

        if (FAILED(hr) && (*pbstrURI))
        {
            SysFreeString(*pbstrURI);
            *pbstrURI = NULL;
        }
    }

    if (bNodePushed)
    {
        hr = m_pNSManager->popContext();
    }

    return hr;
}

/*++

Routine Name:
    getPrefix
Routine Description:

    Returns predefined Prefix for the input namespace URI if one already exists

Arguments:
    pContext - Current context node
    pszUri - namespace URI for which prefix is to be obtained
    pbstrPrefix - prefix string to be returned, if NULL routine just checks for
                    existence of prefix

Return Value:
    HRESULT Completion status code
    S_OK - on success
    S_FALSE - on failure

--*/
HRESULT
OEMPTXMLHandler::getPrefix(
    __in IXMLDOMElement *pContext,
    __in PCWSTR pszUri,
    __deref_opt_out_opt BSTR *pbstrPrefix
    ) const
{
    HRESULT hr = S_OK;
    BOOL bNodePushed = FALSE;
    INT cchPrefix = 0;

    if (!pContext || !pszUri)
            return E_INVALIDARG;

    //Push context "Deep" to get info abt all URI prefix mappings defined so far in the current DOM document
    hr = m_pNSManager->pushNodeContext(pContext, TRUE);

    if (SUCCEEDED(hr))
    {
        bNodePushed = TRUE;

        // get the length first, then alloc the string

        hr = m_pNSManager->getPrefix(pszUri, 0, NULL, &cchPrefix);
    }


    if (S_OK == hr && pbstrPrefix)
    {

        *pbstrPrefix = NULL;
        cchPrefix++;

        if (SUCCEEDED(hr))
        {
            *pbstrPrefix = SysAllocStringLen(NULL, cchPrefix);

            if (!(*pbstrPrefix))
                hr = E_OUTOFMEMORY;
        }

        if (SUCCEEDED(hr))
        {
            hr = m_pNSManager->getPrefix(pszUri, 0, *pbstrPrefix, &cchPrefix);

        }

        if (FAILED(hr) && (*pbstrPrefix))
        {
            SysFreeString(*pbstrPrefix);
            *pbstrPrefix = NULL;
        }
    }

    // Note: MSXML NSManager returns S_FALSE for an undeclared URI, and E_FAIL for an undeclared
    // prefix, hence it is safe to check for both here.

    if (E_FAIL == hr || S_FALSE == hr)
    {
        // NS manager didn't find the prefix. Tweak the result to S_FALSE
        hr = S_FALSE;
    }

    if (bNodePushed)
    {
        m_pNSManager->popContext();
    }

    return hr;
}

/*++

Routine Name:
    declarePrefix

Routine Description:

    This declares a new prefix for a given URI.  This routine will always generate a new
    prefix definition, regardless of whether the URI is already defined.  Use getPrefix
    to prevent duplicate definitions of namespace URI prefixes.  If the preferred prefix is
    already in use or no preferred prefix is supplied the namespace will be declared using
    a generated namespace name.

Arguments:
    pContext - The context in which you want to ensure that the prefix is properly declared
        in.  This is where we check to see if the prefix is already in use.  However, if not
        in use, the prefix will always be declared in the root of the document.  This gaurantees
        2 things: The prefix is declared in the entire document (though it may be overridden
        locally in other places), and the prefix has the expected meaning at the passed in pContext.
    pszUri - The URI to associate with the prefix.
    pszPreferredPrefix - prefix to use if not already defined, can be a BSTR or PCWSTR, either
        is acceptable
    pbstrNewPrefix - The actual prefix that was declared, which may be different from the
        preferred prefix

Return Value:
    HRESULT Completion status code
    S_OK - on success
    S_FALSE - on failure

--*/
HRESULT
OEMPTXMLHandler::declarePrefix(
    __in IXMLDOMElement *pContext,
    __in PCWSTR pszUri,
    __in_opt PCWSTR pszPreferredPrefix,
    __out BSTR *pbstrNewPrefix
    )
{
    HRESULT hr = S_OK;

    // These two strings are tied ... the second string is used to print the number over top of a
    // copy of the first string, so chances are, if you change one, you need to change the other.
    PCWSTR szPrefixChars = L"ns0000";
    PCWSTR szPrefixPrintfPattern = L"ns%.4X";

    if (!(pContext && pszUri && pbstrNewPrefix))
    {
        return E_INVALIDARG;
    }

    *pbstrNewPrefix = NULL;

    // Check if the preferred prefix exists...
    //
    // If we got S_OK, the prefix exists, if we got E_*, bail out
    // anyway.  Only declare the prefix if we successfully decided
    // it doesn't exist.

    if (pszPreferredPrefix)
    {
        INT unused;

        if (SUCCEEDED(hr))
        {
            hr = m_pNSManager->pushNodeContext(pContext, TRUE);
        }

        if (SUCCEEDED(hr))
        {
            hr = m_pNSManager->getURI(pszPreferredPrefix, NULL, NULL, &unused);

            // If the prefix is already defined, MSXML will return S_FALSE.  In that
            // case, we don't want to declare another namespace with the same prefix
            if (S_FALSE != hr)
            {
                pszPreferredPrefix = NULL;
            }

            hr = m_pNSManager->popContext();
        }

        if (SUCCEEDED(hr) && pszPreferredPrefix)
        {
            *pbstrNewPrefix = SysAllocStringLen(pszPreferredPrefix, (UINT) wcslen(pszPreferredPrefix));
        }
    }

    // If we don't have a workable prefix, try to generate a namespace
    // Namespaces are in the form of NSxxxx where the x's are hex digits,
    // and there are sizeof(m_nextPrefix)*2 of them
    if (SUCCEEDED(hr) && !(*pbstrNewPrefix))
    {
        BOOL bFound = TRUE;
        UINT cchLength = (UINT) wcslen(szPrefixChars);
        *pbstrNewPrefix = SysAllocStringLen(NULL, cchLength);
        if (!(*pbstrNewPrefix))
        {
            hr = E_OUTOFMEMORY;
        }
        if (SUCCEEDED(hr))
        {
            do
            {
                INT unused;

                // We're really unlikely to hit this.   If we do we probably
                // have bigger problems.  but put a reasonable cap on the
                // generated namespaces anyway.
                if (((UINT)m_dwNextIndex) >= (UINT)0xFFFF)
                {
                    // We could reset the counter here, but it's very unlikely to
                    // help matters, and could end up costing tons of CPU time if
                    // caller ignored the error or retried.
                    hr = E_FAIL;
                }

                if (SUCCEEDED(hr))
                {
                    // now fill in the real value.
                    hr = StringCchPrintf(*pbstrNewPrefix, cchLength+1, szPrefixPrintfPattern, (INT)m_dwNextIndex);
                }

                if (SUCCEEDED(hr))
                {
                    // look up the URI associated with the prefix.
                    bFound = (S_OK == m_pNSManager->getURI(*pbstrNewPrefix, pContext, NULL, &unused));
                }

                m_dwNextIndex++;

            } while(SUCCEEDED(hr) && bFound);

            if (bFound)
            {
                SysFreeString(*pbstrNewPrefix);
                *pbstrNewPrefix = NULL;
            }
        }
    }

    if (SUCCEEDED(hr) && *pbstrNewPrefix)
    {
        // We'll always put declarations at the root
        if (SUCCEEDED(hr))
        {
            // Create the namespace declaration string
            UINT cchDeclLength = (UINT)(wcslen(L"xmlns:") + wcslen(*pbstrNewPrefix));

            BSTR bstrDecl = SysAllocStringLen(NULL, cchDeclLength);
            if (!bstrDecl)
            {
                hr = E_OUTOFMEMORY;
            }

            // Add the new declaration to the root of the document.  This is important because
            // we really don't want to re-declare the namespace at every element.  Just starts
            // to look really messy, and adds a lot of bloat.
            if (SUCCEEDED(hr))
            {
                hr = StringCchPrintf(bstrDecl, cchDeclLength+1, L"xmlns:%s", *pbstrNewPrefix);
            }

            if (SUCCEEDED(hr))
            {
                hr = CreateXMLAttribute(m_pRootElement, bstrDecl, L"", pszUri);
            }

            SysFreeString(bstrDecl);
            bstrDecl = NULL;

            // Explicitly let the namespace manager know about our prefix

            if (SUCCEEDED(hr))
            {
                hr = m_pNSManager->declarePrefix(*pbstrNewPrefix, pszUri);
            }
        }
    }

    if (FAILED(hr))
    {
        SysFreeString(*pbstrNewPrefix);
        *pbstrNewPrefix = NULL;
    }

    return hr;
}

/*++
Routine Name:
    DeleteFeatureNode
Routine Description:

    Deletes given input Feature node and all its children from DOM tree

Arguments:

    pParent - Parent node of the XML DOM node to be deleted
    pElement - XML DOM node to be deleted

Return Value:
    HRESULT Completion status code
    S_OK - On Success
    E_* - On failure
--*/
HRESULT
OEMPTXMLHandler::DeleteFeatureNode(
    __in_opt IXMLDOMNode *pParent,
    __in IXMLDOMNode *pElement
    )
{
    HRESULT hr = S_OK;

    IXMLDOMNode *pRemovedElement = NULL;

    if (!pElement)
    {
        return E_INVALIDARG;
    }

    //
    // if no parent node specified assume root of DOM tree as parent
    //

    if (!pParent)
    {
        pParent = m_pRootElement;
    }

    //
    // MSXML function removeChild removes the given child of the XML
    // element and all its children, even the Attribute nodes defined
    // for given element node if any are removed. Hence we do not need
    // to worry about recursively removing all the children of the
    // given node before removing it
    //

    hr = pParent->removeChild(pElement, &pRemovedElement);

    if (pRemovedElement)
    {
        pRemovedElement->Release();
    }

    return hr ;
}

